#!/usr/bin/python
# -*- coding: utf-8 -*-

from OpenGL.arrays import vbo
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
from point import Point
from numpy import array
import sys, math, os
from collections import namedtuple
#from obj_reader import ObjReader


EXIT = -1
FIRST = 0
vb = None
SIZE = 2 # Veraendert die Groesse des Objekts (1=Standard)
SIZE_OF_FLOAT = 3
COUNT_VERTEX_COORDINATES = 3

file_name = 'cow.obj'
Boundingbox = namedtuple('Boundingbox', 'min_x min_y min_z max_x max_y max_z')
BBOX_ELEMENTS = 6
# neue Klasse Boundingbox um namedtuple erstellenzu koennen

# punkte liste global, da von mehreren funktionen benoetigt
points = []

# punkte-array lenge, kann variieren je nach indexed faceset
#array_length = 0
array_len = 0

# schrittweite wann der naechste vertex beginnt, kann variieren je nach indexed faceset
# FIXME: warum wenn man hier 9 eintraegt sehen punkte komisch aus
stride = 0

x_angle = 0
y_angle = 0
z_angle = 0
# Winkel um die Objekt (Punkte) rotiert werden sollen, initial natuerlich 0

middle_point = None
# Mittelpunkt global halten, da er in display() benoetigt wird aber nur einma berr werden muss

def init(width, height):
   """ Initialize an OpenGL window """
   glClearColor(0.0, 0.0, 0.0, 0.0)         #background color
   glMatrixMode(GL_PROJECTION)              #switch to projection matrix
   glLoadIdentity()                         #set to 1
   glOrtho(-1.5, 1.5, -1.5, 1.5, -1.0, 1.0) #multiply with new p-matrix
   glMatrixMode(GL_MODELVIEW)               #switch to modelview matrix

##FIXME bounding box ist seltsam meine ich
## das sind die richtigen werte der Kuh die genau im Mittelpunkt steht
##   [[-0.522196, -0.319838, -0.170141], [0.522196, 0.319838, 0.170141]]
##[0.0, 0.0, 0.0]  center 
   
def get_bounding_box(lis # [['ax', 'ay', 'az'],...]
                     ):
    """Gibt eine 3D BoundingBox anhand kleinseter und groesster Koordinaten, als tuple zurueck"""
    print lis[:1], ' bbox lis'
    lis_sorted = zip(*lis) # zip(*lis) entpackt die liste um eine Tiefe
    # zip(['ax', 'ay', 'az'], ['bx', 'by', 'bz'], ['cx', 'cy', 'cz']) =
    # [('ax', 'bx', 'cx'), ('ay', 'by', 'cy'), ('az', 'bz', 'cz')]
    # FIXME: format der liste anpassen out of range bei manchen objs
    return Boundingbox(min(lis_sorted[0]), min(lis_sorted[1]), min(lis_sorted[2]),
                       max(lis_sorted[0]), max(lis_sorted[1]), max(lis_sorted[2]))
     
def get_middle_point(bbox):
   """gibt Mittelpunkt (Arithmetisches Mittel) der 3D Boundingbox zurueck"""
   middle_x = (bbox.min_x + bbox.max_x) / 2
   middle_y = (bbox.min_y + bbox.max_y) / 2
   middle_z = (bbox.min_z + bbox.max_z) / 2
   return Point(middle_x, middle_y, middle_z)
   
def display():
   """ Render all objects"""
   # moeglichst keine grossen berrechnenunge da dies die Callback Funktion ist die im
   # MainLoop aufgerufen wird
   # besser auf globale Member zurueckgreifen z.B. VBOs  die im Grafikspeicher bereit liegen
   global middle_point, scalar_factor, x_angle, y_angle, z_angle
   
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) #clear screen
   
   # Set color to light gray
   glColor3f(.75, .75, .75)

   # Set drawstyle, steht nur FRONT hier wird objekt nur gezeichnet wenns im Uhrzeigersinn da steht
   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

   # Render Vertex Buffer Object, nimm jetzt das VBO (Punkte die im Grafikspeicher liegen)
   vb.bind()

   # Faehigkeit hinzufuegen, sag was das VBO ist, es ist ein Punkte-Array
   glEnableClientState(GL_VERTEX_ARRAY)
#   glEnableClientState(GL_NORMALARRAY)
   # erklaeren wie VBO strukturiert ist und wo es im Speicher liegt
   ## glVertexPointerf(vb) # vereinfachte methode fuer babies
   glVertexPointer(SIZE_OF_FLOAT, GL_FLOAT, stride, vb)
   
   # glDrawArrays(Zeichenmethode, start, anzahl), Zeichne die VBO Bytes auf diese Art
   # GL_TRIANGLES ist richtig
   glDrawArrays(GL_TRIANGLES, 0, array_len)
   vb.unbind()
   glDisableClientState(GL_VERTEX_ARRAY)


   # Vorherige Transformation rueckgaengig machen auf ausgangsposition stellen
   # da Punkte immer absolutwerte zum Ursprung haben
   glLoadIdentity()

   # Rotation glRotate(angle, x, y, z)
   # angle ist der Winkel in Grad mit dem gedreht wird
   # x,y,z ist der Vektor um den gedreht wird (Achse)
   glRotate(x_angle, 1, 0, 0) # um x Achse drehen
   glRotate(y_angle, 0, 1, 0) # um y Achse drehen
   glRotate(z_angle, 0, 0, 1) # um z Achse drehen

   #Skalierung, verzerrung
   glScalef(scalar_factor, scalar_factor, scalar_factor)

   # Translation, um Ursprung legen, verschieben um Vekor (x,y,z)
   # aktuelle Matrix wird mit Verschiebungsmatrix multiplizert
   glTranslatef(-middle_point.x, -middle_point.y, -middle_point.z)
   # Wenn die aktuelle Matrix GL_MODELVIEW oder GL_PROJECTION ist,
   # werden alle nach einem Aufruf von glTranslate gerenderten Objekte
   # verschoben. Mittels glPushMatrix und glPopMatrix lassen sich noch nicht
   # verschobene Koordinatensysteme sichern bzw. wieder herstellen. 

   # Flush commands
##   glFlush() # brauch man bei GLUT verwendung nicht mehr (erzwingt die ausfuehrung aller anstehenden ogl befehle)
   glutSwapBuffers() # Backbuffer in Frontbuffer kopieren (GLUT funktion, macht glFlush() ueberfluessig

def reshape(width, height):
   """ adjust projection matrix to window size"""
   glViewport(0, 0, width, height)
   glMatrixMode(GL_PROJECTION)
   glLoadIdentity()
   if width <= height:
       glOrtho(-1.5, 1.5,
               -1.5*height/width, 1.5*height/width,
               -1.0, 1.0)
   else:
       glOrtho(-1.5*width/height, 1.5*width/height,
               -1.5, 1.5,
               -1.0, 1.0)
   glMatrixMode(GL_MODELVIEW)


def keyPressed(key, x, y):
    """ handle keypress events """
    global x_angle, y_angle, z_angle    
    
    if key == chr(27): # chr(27) = ESCAPE
        sys.exit()
    # rotiert Punkt um 10 Grad
    # addiere / subtrahiere 10 Grad vom aktuellen Winkel % 360 um im Kreis zu bleiben
    if key == 'x':
        x_angle += 10 % 360
    if key == 'X':
        x_angle -= 10 % 360
    if key == 'y':
        y_angle += 10 % 360
    if key == 'Y':
        y_angle -= 10 % 360
    if key == 'z':
        z_angle += 10 % 360
    if key == 'Z':
        z_angle -= 10 % 360

    glutPostRedisplay()
    # aktuelles Window zum neuzeichnen markiert, mehrere aufrufe glutPostRedisplay()
    # erzeugen nur einmal den Marker zum neu Zeichnen
    # kann auch in der display() benutzt werden

def mouse(button, state, x, y):
   """ handle mouse events """
   if button == GLUT_LEFT_BUTTON and state == GLUT_DOWN:
       print "left mouse button pressed at ", x, y

def mouseMotion(x,y):
   """ handle mouse motion """
   print "mouse motion at ", x, y

def menu_func(value):
   """ handle menue selection """
   print "menue entry ", value, "choosen..."
   if value == EXIT:
       sys.exit()
   glutPostRedisplay()


def get_scalar_factor(bbox):
    """Gibt Skalierungsfaktor anhand der Boundingbox zurueck"""
    global SIZE
    scalar_factor = SIZE / (max(bbox.max_x - bbox.min_x, bbox.max_y - bbox.min_y, bbox.max_z - bbox.min_z))
    # SIZE / laengste Seite der Boundingbox
    return scalar_factor
 
## def get_vertex_list(reader # ObjReader objekt
##                      ):
##    """Gibt liste der Punkte zurueck wie sie zum initialisieren eines VBO benoetigt werden"""
##    vertex_list = []
         
##    # TODO: assert parser gibt nur gueltige obj datei zurueck
##    vertices = reader.vertices
##    vertex_normals = reader.vertex_normals
##    textures = reader.textures
      
##    # [ [vetexliste], [normalenliste], [texturliste]   
##    lis = [lis for lis in (vertices, vertex_normals, textures) if lis != []]

##    # TODO: andere facestets testen, evtl diese methode in den parser setzen 
##    if vertex_normals and textures:
##       vertex_list = zip(lis[0], lis[1], lis[2])
##    elif vertex_normals:
##       vertex_list = zip(lis[0], lis[1])
##    else:
##       vertex_list = lis[0]
##    print vertex_list[0]
##    return vertex_list
   
def main():
   """Hauptfunktion zur steuerung des Ablaufs und initialisiert OpenGL Kontext (GLUT Window) etc"""
   global file_name, middle_point, scalar_factor
   global vb, points, array_len

   # module die weit weg liegen sichtber machen
   sys.path.append('/home/mi/ptreb001/svn/computergrafikschwanecke/CGProjekt/skybox_test_umgebung/handler/')
   from obj_reader import ObjReader

   print ObjReader
   # Kommandozeilenparameter checken
   # FIXME: eigene methode mit python argparse
   if len(sys.argv) > 1:
      file_name = sys.argv[1]

   # Einzulesende Punktedatei
   filename = "cow.obj"

   # parser- bzw. reader-objekt instanziiieren
   reader = ObjReader(filename)
   
   # Obj-Datei einlesen und enthaltene Daten bereitstellen
   ## reader.read_raw_file()
   reader.read_file()
   print dir(reader)
   # globale point liste initialisieren
   ## points = reader.raw_points
   points = reader.get_vertex_list()
   array_len = len(points)
   
   # VBO-Objekt initialisieren mit Punktearray
   vb = vbo.VBO(array(points,'f'))   

   # Boundingbox berrechnen FIXME: format noch korrigieren hier
   bbox = get_bounding_box(points)

   # Mittelpukt berrechnen
   middle_point = get_middle_point(bbox)

   # Skalierungsfaktor berrechnen
   scalar_factor = get_scalar_factor(bbox)
   
   print "mittelpunkt ", middle_point
   print "scalar faktor ", scalar_factor
   
   #############################################################
   # OpenGL Kontext erstellen (OS Abhaengig)
   #############################################################
   # Hack for Mac OS X
   cwd = os.getcwd()
   glutInit(sys.argv)
   os.chdir(cwd)
   
   
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
   ######   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
   
   glutInitWindowSize(500, 500)
   glutCreateWindow(" openGL/GLUT ")
   
   glutDisplayFunc(display)     #register display function
   
   
   glutReshapeFunc(reshape)     #register reshape function
   glutKeyboardFunc(keyPressed) #register keyboard function 
   glutMouseFunc(mouse)         #register mouse function
   glutMotionFunc(mouseMotion)  #register motion function
   #glutCreateMenu(menu_func)    #register menue function
   
   
   
   
   glutAddMenuEntry("First Entry",FIRST) #Add a menu entry
   glutAddMenuEntry("EXIT",EXIT)         #Add another menu entry
   glutAttachMenu(GLUT_RIGHT_BUTTON)     #Attach mouse button to menue
   
   init(500,500) #initialize OpenGL state
   # Initialize OpenGL context
   
   glutMainLoop() #start even processing
   
   
if __name__ == "__main__":
   main()
